package net.towee.server.factories;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Named;

import net.towee.model.bittorrent.TorrentData;
import net.towee.model.bittorrent.TorrentItem;
import net.towee.model.bittorrent.impl.TorrentDataImpl;
import net.towee.model.bittorrent.impl.TorrentItemImpl;
import net.towee.server.CoreSettings;
import net.towee.server.bittorrent.TorrentFactory;
import net.towee.server.bittorrent.bee.BEDecoder;
import net.towee.server.bittorrent.bee.BEEncoder;
import net.towee.server.bittorrent.bee.BEValue;
import net.towee.server.persistence.AttachmentStore;
import net.towee.server.persistence.TorrentStore;
import net.towee.shared.exceptions.PersistenceException;

import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;

/**
 * Implementation of torrent factory
 * 
 * @author c58
 * 
 */
public class TorrentFactoryImpl implements TorrentFactory {
	private static final Logger LOG = LoggerFactory.getLogger(TorrentFactoryImpl.class);
	private final TorrentStore torrentStore;
	private final String announceUrl;
	private final AttachmentStore attStore;
	private static final ThreadLocal<Map<String, BEValue>> decodedTorrent = new ThreadLocal<Map<String, BEValue>>();

	@Inject
	public TorrentFactoryImpl(TorrentStore torrentStore, AttachmentStore store,
			@Named(CoreSettings.ANNOUNCE_URL) String announceUrl) {
		this.torrentStore = torrentStore;
		this.announceUrl = announceUrl;
		this.attStore = store;
	}

	@Override
	public TorrentData createTorrent(InputStream torrentStream, String attId) {
		try {
			// Decode torrent
			BEDecoder decoder = new BEDecoder(torrentStream);
			BEValue torrent = decoder.decode();
			Map<String, BEValue> metainfo = torrent.mapValue();

			// Get info field and calculate infoHash
			BEValue info = metainfo.get("info");
			String infohash = DigestUtils.shaHex(new BEEncoder().encode(info));

			// Put announce url
			metainfo.put("announce", new BEValue(announceUrl));
			if (metainfo.containsKey("announce-list"))
				metainfo.get("announce-list").listValue()
						.add(0, new BEValue(Arrays.asList(new BEValue(announceUrl))));
			else
				metainfo.put("announce-list",
						new BEValue(Arrays.asList(new BEValue(Arrays.asList(new BEValue(announceUrl))))));

			// Update torrent
			attStore.replaceAttachment(attId,
					new ByteArrayInputStream(new BEEncoder().encode(new BEValue(metainfo))));

			// Create torrent data
			TorrentData torrentData = new TorrentDataImpl(infohash);

			// Set decoded torrent to local thread storage
			decodedTorrent.set(metainfo);

			// Register torrent
			if (torrentStore.createTorrent(torrentData))
				return torrentData;
			else
				return torrentStore.getTorrent(infohash);
		} catch (IOException e) {
			LOG.error("Error while decoding torrent file! ", e);
		} catch (PersistenceException e) {
			LOG.error("Error while getting torrent with hash", e);
		}

		return null;
	}

	@Override
	public long getContentSize() {
		Preconditions.checkNotNull(decodedTorrent.get());
		Map<String, BEValue> info = decodedTorrent.get().get("info").mapValue();

		if (info.containsKey("length"))
			return info.get("length").longValue();
		else if (info.containsKey("files")) {
			long size = 0;

			for (BEValue file : info.get("files").listValue())
				if (file.mapValue().containsKey("length"))
					size += file.mapValue().get("length").longValue();

			return size;
		}

		return 0;
	}

	@Override
	public List<TorrentItem> getItemsList() {
		Preconditions.checkNotNull(decodedTorrent.get());
		Map<String, BEValue> info = decodedTorrent.get().get("info").mapValue();
		List<TorrentItem> items = new ArrayList<TorrentItem>();

		// Get tree
		if (info.containsKey("files"))
			for (BEValue fileTrunk : info.get("files").listValue()) {
				Map<String, BEValue> file = fileTrunk.mapValue();
				List<BEValue> pathList = file.get("path").listValue();

				List<String> path = new ArrayList<String>();
				for (BEValue b : pathList.subList(0, pathList.size() - 2))
					path.add(b.stringValue());

				// Create item
				items.add(new TorrentItemImpl(pathList.get(pathList.size() - 1).stringValue(), file.get(
						"length").longValue(), path));
			}

		return items;
	}

	@Override
	public void processTorrent(String accountId, InputStream stream, Appendable out) {
		try {
			// Set announce url
			String announce = announceUrl + "/" + accountId;

			// Write announce url
			out.append("d8:announce");
			out.append(announce.length() + ":" + announce);

			// Write announce list
			out.append("13:announce-listl");
			out.append("l" + announce.length() + ":" + announce + "e");
			out.append("l31:http://retracker.local/announcee");

			// Skip header
			stream.skip(30 + (announceUrl.length()+":"+announceUrl).length()*2);

			// Write announce-list urls and other
			int s;
			while ((s = stream.read()) >= 0)
				out.append((char) s);

		} catch (IOException e) {
			LOG.error("Error while decoding torrent file! ", e);
		}
	}
}